package dkbta.util.plan.subperunit;

import static dkbta.util.Utils.readDOMDocFromXML;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import dkbta.CoordinatorConfig;
import dkbta.ontology.loading.OntologyException;
import dkbta.processing.PlanException;
import dkbta.util.Pair;
import dkbta.util.data.IntervalID;
import dkbta.util.data.StatInfo;



public class TaskParserUtil {
	
	public static IntervalID []getUnitIntervals(IntervalID interval, int numOfUnits){
		IntervalID [] tasks;
		if(numOfUnits == 0){
			return null;
		}
		
		tasks = new IntervalID[numOfUnits];
		int from = interval.getFrom();
		int to = interval.getTo();
		
		int portion = (to - from + 1)/numOfUnits;
		int res = (to - from + 1)%numOfUnits;
		
		int i = 0;
		
		for(; i < numOfUnits-1; i++) {
			if(i<res){
				tasks[i] = new IntervalID(from, from + portion);
				from = from + portion + 1;
			}
			else{
				tasks[i] = new IntervalID(from, from + portion - 1);
				from = from + portion;
			}
		}
		tasks[i] = new IntervalID(Math.min(from, to), to);
		
		return tasks;
	}
	
//	public static void makePlans(String taskFileName, int numOfUnits, ArrayList<StatInfo> statsInfo) throws Throwable {
//
//		 FileOutputStream [] fouts = new FileOutputStream[numOfUnits];
//		 statsInfo = setUnitIDs(statsInfo, numOfUnits);	 
//		 
//		 try{
//			    FileInputStream fstream = new FileInputStream(taskFileName);
//			    DataInputStream in = new DataInputStream(fstream);
//			    BufferedReader br = new BufferedReader(new InputStreamReader(in));
//			    String strLine;
//			    String [] stringOfIntervals = null;
//			    String oldInterval = null;
//			    String temp = null;
//			    String unitString = null;
//			    int unitIndx = 0;
//			    
//			    for(int i = 0; i < numOfUnits; i++){
//			    	fouts[i] = new FileOutputStream("Plan-" + (i+1) + ".xml");
//			    }
//			    
//			    boolean isIntervals = false;
//			    
//			    while ((strLine = br.readLine()) != null)   {
//			    	
//			    	if(strLine.indexOf("<Stat name")>=0){
//			    		break;
//			    	}
//			    	
//			    	if(strLine.indexOf("</Patients>")>=0 && unitIndx>0){
//			    		new PrintStream(fouts[unitIndx-1]).println (unitString);
//			    		unitIndx = 0;
//			    	}
//			    	
//			    	if(strLine.indexOf("id=")>=0 && strLine.indexOf("Unit")<0)//abstraction
//			    	{
//			    		
//			    		oldInterval = strLine.substring(strLine.indexOf("id=")+4, strLine.length() - 2);
//			    		
//			    		Pair<Integer, Integer>[] ids = parsePatientIds(oldInterval);
//						int numOfPatients = getNumOfPatients(ids);
//						stringOfIntervals = parseIntervals(ids, numOfPatients, min(numOfPatients,numOfUnits));
//			    		isIntervals = true;
//			    	}
//			    	
//			    	if(strLine.indexOf("name=")>=0 && strLine.indexOf("inputTableName")<0){
//			    		String abstrFrom = getAbstractionNameFromString(strLine);
//			    		unitIndx = getUnitIDindxForAbstraction(abstrFrom, statsInfo);
//			    		for(int i = 0; i < numOfUnits; i++){
//			    			if(i==unitIndx-1){
//			    				temp = strLine;
//			    				unitString = getRemoteCalcString(stringOfIntervals[i], getStatInterval(abstrFrom, statsInfo), temp);
//			    			}
//			    			else{
//			    				temp = strLine.substring(0, strLine.lastIndexOf('"')+1) + " sendToUnits=" + '"' + unitIndx + '"' + 
//			    							strLine.substring(strLine.lastIndexOf('"')+1);
//			    			}
//			    			new PrintStream(fouts[i]).println (temp);
//			    		}
//			    	}
//			    	else
//			    	{
//			    	
//					    	for(int i = 0; i < numOfUnits; i++){
//					    		if(isIntervals){
//					    			temp = strLine.replaceFirst(oldInterval, stringOfIntervals[i]);
//					    			new PrintStream(fouts[i]).println (temp);
//					    		}
//					    		else{
//					    			new PrintStream(fouts[i]).println (strLine);
//					    		}
//					    	}
//					    	isIntervals = false;
//			    	}
//			    }
//			    
//			    for(int i = 0; i < statsInfo.size(); i++){
//			    	StatInfo stat = statsInfo.get(i);
//			    	int indx = stat.getUnitID() - 1;
//			    	new PrintStream(fouts[indx]).println (stat.toString());
//			    }
//			    			    
//			    for(int i = 0; i < numOfUnits; i++){
//			    	new PrintStream(fouts[i]).println ("\t</Statistical>");
//			    	new PrintStream(fouts[i]).println ("</Plan>");
//			    	fouts[i].close();
//			    }
//			    in.close();
//			    }catch (Exception e){
//			      System.err.println("Error: " + e.getMessage());
//			    }
//
//	 }
	
	public static String getRemoteCalcString(String intervalID, String statInterval, String temp){
			String interval = "";
			try{
				interval = allMinusUnit(statInterval, intervalID);
			}
			catch(Throwable e){
				e.printStackTrace();
			}
			return "\t\t<Patient id=" + '"' + interval + '"' + 
							">\n\t\t\t<Local></Local>\n\t\t\t<Remote>\n\t\t"+temp+"\n\t\t\t</Remote>\n\t\t</Patient>\n";
	}

	public static String getStatInterval(String abstrFrom, ArrayList<StatInfo> statsInfo) {
		String retValue = null;
		for(int i = 0; i < statsInfo.size(); i++){
			if(abstrFrom.equals(statsInfo.get(i).getAbstractedFromName())){
				retValue = statsInfo.get(i).getPatients();
				break;
			}
		}
		return retValue;
	}

	public static int getUnitIDindxForAbstraction(String abstractionNameFromString, ArrayList<StatInfo> statsInfo) {
		int retValue = -1;
		for(int i = 0; i < statsInfo.size(); i++){
			if(abstractionNameFromString.equals(statsInfo.get(i).getAbstractedFromName())){
				retValue = statsInfo.get(i).getUnitID();
				break;
			}
		}
		return retValue;
	}

	public static String getAbstractionNameFromString(String strLine) {
		return strLine.substring(strLine.indexOf('"')+1, strLine.lastIndexOf('"'));
	}

	public static ArrayList<StatInfo> setUnitIDs(ArrayList<StatInfo> statsInfo, int numOfUnits) {
		
		int unitID = 1;
		
		for(int i = 0; i < statsInfo.size(); i++){
			statsInfo.get(i).setUnitID(unitID);
			unitID++;
			
			if(unitID>numOfUnits)	
				unitID = 1; 
		}
		return statsInfo;
	}

	private static Element getAfElement(Element afsElement) throws OntologyException{
		NodeList childNodes = afsElement.getChildNodes();

		int elementNodeCount = 0;
		Element afElement = null;
		for (int i = 0; i < childNodes.getLength(); ++i){
			Node node = childNodes.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE){
				elementNodeCount++;
				afElement = (Element)node;
			}
		}

		if (elementNodeCount != 1){
			throw new OntologyException(
					"A statistical abstraction can be abstracted from only one element and not "
							+ elementNodeCount);
		}
		return afElement;
	}
	
	@SuppressWarnings({ "unchecked", "static-access" })
	public static ArrayList<StatInfo> parseStatisticalAbstractions(Element plan) throws Exception{
		
		Element ontologyElement = CoordinatorConfig.getXmlElement(plan, "Ontology");
		if (ontologyElement == null){
			throw new PlanException("No 'Ontology' element in the task");
		}
		
		Element statsticalNode = CoordinatorConfig.getXmlElement(plan, "Statistical");
		String ontologyFileName = ontologyElement.getAttribute("file");
		Element statisticalOntologyNode = CoordinatorConfig.getXmlElement(readDOMDocFromXML(new File(ontologyFileName)).getDocumentElement(), "Stats");
		
		if (statsticalNode == null || statisticalOntologyNode == null){
			return null; // No statistical abstractions
		}
		
		ArrayList statsInfo = new ArrayList<StatInfo>();
		NodeList statNodes = statsticalNode.getChildNodes();
		NodeList statOntologyNodes = statisticalOntologyNode.getChildNodes();
		
		int length = statNodes.getLength();
		int ontologyLength = statOntologyNodes.getLength();
		
		for (int i = 0; i < length; ++i){
			Node node = statNodes.item(i);
			if (node.getNodeType() != Node.ELEMENT_NODE){
				continue;
			}
			Element statElement = (Element) statNodes.item(i);
			String name = statElement.getAttribute("name");
			String patients = statElement.getAttribute("patients");
			String sendToDB = statElement.getAttribute("sendToDB");
			
			for(int j = 0; j < ontologyLength; j++){
				Node ontologyNode = statOntologyNodes.item(j);
				if (ontologyNode.getNodeType() != ontologyNode.ELEMENT_NODE){
					continue;
				}
				Element statOntologyElement = (Element)statOntologyNodes.item(j);
				String ontologyName = statOntologyElement.getAttribute("name");
				if(name.equals(ontologyName)){
					Element afsElement = CoordinatorConfig.getXmlElement(statOntologyElement, "AbstractedFrom");
					if (afsElement == null){
						throw new OntologyException("No abstracted-from element");
					}

					Element afElement = getAfElement(afsElement);
					String abstractedFromName = afElement.getAttribute("name");
					boolean isPrimitive = afElement.getNodeName().equals("Primitive");
					statsInfo.add(new StatInfo(ontologyName, patients, sendToDB, abstractedFromName, isPrimitive));
					break;
				}
			}
			
		}
		return statsInfo;
	}
	
	public static Pair<Integer, Integer>[] parsePatientIds(String idRangesStr)	throws PlanException{
		
	String[] idRanges = idRangesStr.trim().split(" *, *");
	if (idRanges.length == 0){
		throw new PlanException("No patient ids specified");
	}
	@SuppressWarnings("unchecked")
	Pair<Integer, Integer>[] ids = new Pair[idRanges.length];
	for (int j = 0; j < idRanges.length; ++j){
		String[] idRange = idRanges[j].split(" *- *");
		int rangeLen = idRange.length;
		if (rangeLen > 2){
			throw new PlanException("Invalid patient ids: " + idRangesStr);
		}
		try{
			int id1 = Integer.valueOf(idRange[0]);
			int id2 = rangeLen == 2 ? Integer.valueOf(idRange[1]) : id1;
			ids[j] = new Pair<Integer, Integer>(min(id1, id2), max(id1, id2));
		}catch(NumberFormatException e){
			throw new PlanException("Invalid patient ids: " + idRangesStr);
		}
	}
	return ids;
	}
	
	public static String[] parseIntervals(Pair<Integer, Integer>[] ids, int numOfPatients, int numOfUnits){
		
		int portion = numOfPatients/numOfUnits;
		int res = numOfPatients%numOfUnits;

		String[] patients = new String[numOfUnits];
		int[] idsArray = new int[numOfPatients];
		int currArrayIndx = 0;
		for(int i=0; i<ids.length;i++){
			int first = ids[i].getFirst();
			int second = ids[i].getSecond();
			int len = second - first + 1;
			
			if(len==1){
				idsArray[currArrayIndx] = first;
				currArrayIndx++;
			}
			else{
				for(int j=0; j<len; j++){
					idsArray[currArrayIndx] = first+j;
					currArrayIndx++;
				}
			}
		}
		
		int i = 0;
		int from = 0;
		int to = i<res? portion : portion - 1;
		for(; i<numOfUnits;i++){
			patients[i] = getPatientsAsString(idsArray, from, to);
			from = to + 1;
			to = (i+1)<res? (from + portion) : (from + portion - 1);
		}
		return patients;
	}
	
	private static String getPatientsAsString(int[] idsArray, int from, int to){
		int currValue = idsArray[from];
		String resString = "" + currValue;
		boolean isRange = false;
		for(int i=from+1; i<=to; i++){
			if(currValue+1==idsArray[i]){
				currValue=idsArray[i];
				isRange = true;
			}
			else{
				if(isRange){
					resString+="-"+currValue+",";
					isRange=false;
					currValue=idsArray[i];
				}
				else{
					currValue=idsArray[i];
					resString+=",";
				}
				resString+=currValue;
			}
		}
		if(isRange){
			resString+=("-"+currValue);
		}
			
		return resString;
	}
	
	public static int getNumOfPatients(Pair<Integer, Integer>[] ids){
		int numOfPatients = 0;
		for(int i = 0; i < ids.length; i++){
			numOfPatients += (ids[i].getSecond().intValue() - ids[i].getFirst().intValue() + 1);
		}
		return numOfPatients;
	}
	
	private static String allMinusUnit(String all, String unit) throws Throwable{
		Pair<Integer, Integer>[] unitPairs = parsePatientIds(unit);
		Pair<Integer, Integer>[] allPairs = parsePatientIds(all);
		int allLength = getNumOfPatients(allPairs);
		int unitLength = getNumOfPatients(unitPairs);
		int resLength = allLength - unitLength;
		
		int[] resArray = new int[resLength];
		int resIndx = 0;
		for(int i=0; i<allPairs.length; i++){
			int first = allPairs[i].getFirst().intValue();
			int second = allPairs[i].getSecond().intValue();
			for(int j=first; j<=second; j++){
				if(!isPatientIntoRange(unitPairs, j)){
					resArray[resIndx] = j;
					resIndx++;
				}
			}
		}
		return getPatientsAsString(resArray, 0, resLength-1);
	}
	
	private static boolean isPatientIntoRange(Pair<Integer, Integer>[] unitPairs, int patientID) {
		for(int i=0; i<unitPairs.length; i++)
			if(patientID>=unitPairs[i].getFirst().intValue() && patientID<=unitPairs[i].getSecond().intValue())
				return true;
		return false;
	}

}
